﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mongaba.Core.ServiceContracts;
using Mongaba.Core.Enumerators;
using Mongaba.Core.Exceptions;
using Mongaba.Core.Registers;
using System.Runtime.Remoting;
using System.Threading;
using Mongaba.Core.Listeners;
using System.ServiceModel;
using Mongaba.Core.DataContracts;
using Mongaba.Core.Factory;
using System.Net;
using System.Diagnostics;

namespace Mongaba.Core
{
    [ServiceBehavior(Name = "MogabaCoordinator", InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode=ConcurrencyMode.Multiple)]
    internal class MongabaCoordinator : IMongabaCoordinator
    {
        #region Constructors
        public MongabaCoordinator(GroupContract group)
        {
            _id = Guid.NewGuid().ToString();
            _status = CoordinatorStatus.Sleeping;
            _group = group;
        }

        #endregion

        #region Controls

        public void Start()
        {
            _processes = new Dictionary<string, ProcessContract>();
          
                _knownCoordinators = new Dictionary<string, GroupContract>();
                
                if (thUpdateList == null)
                {
                    thUpdateList = new Thread(new ThreadStart(updateProcessList));
                    thUpdateList.IsBackground = true;
                    thUpdateList.Name = "Coordinator Update List";
                    thUpdateList.Start();
                }
                if (thUpdateScore == null)
                {
                    thUpdateScore = new Thread(new ThreadStart(updateProcessScore));
                    thUpdateScore.IsBackground = true;
                    thUpdateScore.Name = "Coordinator Update Score";
                    thUpdateScore.Start();
                }
                

                if (thUpdateKnownCoordinators == null)
                {
                    thUpdateKnownCoordinators = new Thread(new ThreadStart(updateKnownCoordinators));
                    thUpdateKnownCoordinators.IsBackground = true;
                    thUpdateKnownCoordinators.Name = "Coordinator Update Known Coordinators";
                    thUpdateKnownCoordinators.Start();
                }

                if (thCheckGroups == null)
                {
                    thCheckGroups = new Thread(new ThreadStart(checkGroups));
                    thCheckGroups.IsBackground = true;
                    thCheckGroups.Name = "Coordinator Check Groups";
                    thCheckGroups.Start();
                }

                ResponseListener.Start();
                ResponseListener.ResponseReceivedEvent += new EventHandler(ResponseListener_ResponseReceivedEvent);
                CoordinatorListener.Start();
                _status = CoordinatorStatus.Normal;
           
        }
        public void Stop()
        {
            ResponseListener.Stop();
            ResponseListener.ResponseReceivedEvent -= new EventHandler(ResponseListener_ResponseReceivedEvent);
            CoordinatorListener.Stop();

            _status = CoordinatorStatus.Stopped;

            Console.WriteLine("Abortando thUpdateKnownCoordinators.");
            thUpdateKnownCoordinators.Abort();
            thUpdateKnownCoordinators.Join();

            Console.WriteLine("Abortando thCheckGroups.");
            thCheckGroups.Abort();
            thCheckGroups.Join();

            Console.WriteLine("Abortando thUpdateList.");
            thUpdateList.Abort();
            thUpdateList.Join();     

            Console.WriteLine("Abortando thUpdateScore.");
            thUpdateScore.Abort();
            thUpdateScore.Join();

        }

        public void Pause()
        {
            Console.WriteLine("Pausando a coordenação na máquina");
            ResponseListener.Stop();
            ResponseListener.ResponseReceivedEvent -= new EventHandler(ResponseListener_ResponseReceivedEvent);
            CoordinatorListener.Stop();
            _status = CoordinatorStatus.Sleeping;

        }

        
        #endregion

        #region Fields
        string _id;
        Dictionary<string, GroupContract> _knownCoordinators = new Dictionary<string, GroupContract>();
        Dictionary<string, ProcessContract> _processes = new Dictionary<string, ProcessContract>();
        GroupContract _group;
        CoordinatorStatus _status;
        #endregion

        #region Upate Process List

        Thread thUpdateList = null;

        void updateProcessList()
        {

            while (true)
            {
                try
                {
                    Thread.Sleep(Utilities.ProcessListUpdateDelay);

                    if (_status == CoordinatorStatus.Normal)
                    {
                        lock (_processes)
                        {
                            _status = CoordinatorStatus.Updating;

                            ProcessContract[] processes = _processes.Values.ToArray();
                            foreach (ProcessContract p in processes)
                            {
                                Console.WriteLine("Enviando a lista atualizada para: " + p.Address);
                                try
                                {
                                    if (p.Address == Utilities.HostIp.ToString())
                                    {
                                        IMongabaProcess proxy = ProcessFactory.GetLocalInstance();
                                        proxy.ReveiveProcessList(processes);

                                    }
                                    else
                                    {
                                        IMongabaProcess proxy = ProcessFactory.GetRemoteInstance(p.Address);
                                        proxy.ReveiveProcessList(processes);
                                        ProcessFactory.RemoveRemoteInstanceConnection(p.Address);
                                    }
                                }
                                catch (ThreadAbortException)
                                {
                                    Console.WriteLine("Thread de atualização de lista de processos terminou.");
                                    return;

                                }
                                catch (Exception ex)
                                {
                                    Console.WriteLine("Erro na atualização de processos: " + ex.Message);
                                    _processes.Remove(p.Id);
                                }
                            }
                            _status = CoordinatorStatus.Normal;

                        }
                    }
                    else
                    {
                        Console.WriteLine(String.Format("O processo está {0}. Lista não atualizada", _status.ToString()));
                    }
                }
                catch (ThreadAbortException)
                {
                    Console.WriteLine("Thread de atualização de lista de processos terminou.");
                    return;
                }
            }

        }

        #endregion

        #region Update Score
        Thread thUpdateScore = null;


        void updateProcessScore()
        {

            while (true)
            {
                try
                {
                    Thread.Sleep(Utilities.ProcessScoreUpdateDelay);

                    if (_status == CoordinatorStatus.Normal)
                    {
                        lock (_processes)
                        {
                            Console.WriteLine("Atualização de score começou.");
                            _status = CoordinatorStatus.Updating;

                            ProcessContract[] processes = _processes.Values.ToArray();
                            if (processes.Where(x => x.Status == ProcessStatus.Observation).Count() > 0)
                            {
                                bool received = false;
                                ProcessContract pNewCoordContract = processes.Where(x => x.Status == ProcessStatus.Observation).First();
                                IMongabaProcess newCoordProxy = null;
                                try
                                {
                                    newCoordProxy = ProcessFactory.GetRemoteInstance(pNewCoordContract.Address);
                                    received = newCoordProxy.ReceiveCoordination();
                                }
                                catch (ThreadAbortException)
                                {
                                    return;
                                }
                                catch (Exception ex)
                                {
                                    Console.WriteLine(String.Format("Erro no repasse de coordenação: {0}", ex.Message));
                                    continue;
                                }
                                if (received)
                                {
                                    Console.WriteLine("Repasse de coordenação iniciado.");
                                    _status = CoordinatorStatus.Transfering;
                                    foreach (ProcessContract p in processes)
                                    {
                                        if (pNewCoordContract.Address != p.Address)
                                        {
                                            try
                                            {
                                                if (p.Address != Utilities.HostIp.ToString())
                                                {
                                                    IMongabaProcess proxy = ProcessFactory.GetRemoteInstance(p.Address);
                                                    proxy.ReceiveNewCoordinator(pNewCoordContract.Address);
                                                    ProcessFactory.RemoveRemoteInstanceConnection(p.Address);
                                                }
                                                else
                                                {
                                                    IMongabaProcess proxy = ProcessFactory.GetLocalInstance();
                                                    proxy.ReceiveNewCoordinator(pNewCoordContract.Address);
                                                }
                                            }
                                            catch (ThreadAbortException)
                                            {
                                                return;                                                
                                            }
                                            catch (Exception ex)
                                            {
                                                Console.WriteLine("Erro de transferência de processos: " + ex.Message);
                                                continue;
                                            }
                                        }
                                    }

                                    _status = CoordinatorStatus.Sleeping;
                                    Console.WriteLine("Repasse de coordenação concluído");
                                    Pause();
                                    continue;
                                }
                            }


                            foreach (ProcessContract p in processes)
                            {
                                try
                                {
                                    if (p.Address == Utilities.HostIp.ToString())
                                    {
                                        IMongabaProcess proxy = ProcessFactory.GetLocalInstance();
                                        p.Score = proxy.getScore();
                                        proxy.RenewScore();
                                    }
                                    else
                                    {
                                        IMongabaProcess proxy = ProcessFactory.GetRemoteInstance(p.Address);
                                        p.Score = proxy.getScore();
                                        proxy.RenewScore();
                                        ProcessFactory.RemoveRemoteInstanceConnection(p.Address);
                                    }
                                }
                                catch (ThreadAbortException)
                                {
                                    return;                                                
                                }
                                catch (Exception ex)
                                {
                                    Console.WriteLine("Erro de atualização de score: " + ex.Message);
                                    _processes.Remove(p.Id);
                                    continue; 
                                }
                            }
                            _status = CoordinatorStatus.Normal;

                            if (_processes.Count > 0)
                            {

                                ProcessContract maxProcess = _processes.Values.Where(x => x.Score == _processes.Values.Max(z => z.Score)).First();
                                if (maxProcess.Address != getHostProcessContract().Address)
                                {
                                    maxProcess.Status = ProcessStatus.Observation;
                                    Console.WriteLine(String.Format("Colocando o processo em observação. Id: {0} IP: {1}", maxProcess.Id, maxProcess.Address));
                                }
                            }

                        }

                        Console.WriteLine("Atualização de score terminou.");
                    }
                    else
                    {
                        Console.WriteLine(String.Format("O processo está {0}. Score não atualizado.", _status.ToString()));
                    }
                }
                catch (ThreadAbortException)
                {
                    Console.WriteLine("Thread de atualização de score terminou.");
                    return;
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Erro. " + ex.Message + "Stack trace: " + ex.StackTrace);
                    continue;
                }
            }

            
        }
        #endregion

        #region IMongabaCoordinator Members


        public ProcessContract getHostProcessContract()
        {
            try
            {
                return ProcessFactory.GetLocalInstance().getContract();
            }
            catch
            {
                return null;
            }
        }

        public ProcessContract[] getProcesses()
        {
            return _processes.Values.ToArray();
        }

        public bool RegisterProcess(ProcessContract p)
        {
                           
                lock (_processes)
                {
                    _status = CoordinatorStatus.AddingProcess;
                    if (_processes.ContainsKey(p.Id))
                    {
                        _status = CoordinatorStatus.Normal;
                        throw new ProcessAlreadyExistsException();
                    }
                    else
                    {
                        _processes.Add(p.Id, p);
                        Console.WriteLine(String.Format("Novo usuário adicionado: {0}.", p.User.Name));
                        _status = CoordinatorStatus.Normal;

                        return true;
                    }

                }
           
        }

        public void UnRegisterProcess(string processId)
        {
            try
            {
                lock (_processes)
                {
                    _status = CoordinatorStatus.RemovingProcess;
                    _processes.Remove(processId);
                    _status = CoordinatorStatus.Normal;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Erro em remover o processo: " + ex.Message);
            }
        }

        public GroupContract getGroup()
        {
            return _group;
        }

        public string getId()
        {
            return _id;
        }

        public CoordinatorStatus getStatus()
        {
            return _status;
        }

        public CoordinatorContract[] getKnownCoordinators()
        {
            return null;
        }

        public CoordinatorContract getContract()
        {
            CoordinatorContract c = new CoordinatorContract();
            c.Address = Utilities.HostIp.ToString();
            c.Group = _group;
            c.Id = _id;
            c.Status = _status;
            return c;
        }



        public bool Ping()
        {
            return this._status != CoordinatorStatus.Sleeping;
        }

        #endregion

        #region KnownCoordinators
        Thread thUpdateKnownCoordinators = null;
        Thread thCheckGroups = null;
        void updateKnownCoordinators()
        {
            while (true)
            {
                try
                {
                    Thread.Sleep(Utilities.KnownCoordinatorsCheckerDelay);

                    if (_status != CoordinatorStatus.Sleeping)
                    {
                    
                        lock (_knownCoordinators)
                        {
                            Console.WriteLine("A busca por grupos começou.");
                            _knownCoordinators.Clear();
                            ResponseListener.Refresh();
                        }
                    }
                    else
                    {
                        Console.WriteLine("O processo está Sleeping. Lista de coordenadores conhecidos não atualizada"); 
                    }
                }
                catch (ThreadAbortException)
                {
                    Console.WriteLine("Thread de atualização de lista de coordenadores conhecidos terminou.");
                    return;
                }
            }

            
        }

        void ResponseListener_ResponseReceivedEvent(object sender, EventArgs e)
        {
            foreach (IPAddress ad in ResponseListener.GetResponseList())
            {
                if (ad.ToString() != Utilities.HostIp.ToString())
                {
                    lock (_knownCoordinators)
                    {
                        if (!_knownCoordinators.ContainsKey(ad.ToString()))
                        {
                            try
                            {
                                
                                GroupContract contract = CoordinatorFactory.GetRemoteInstance(ad.ToString()).getGroup();
                                _knownCoordinators.Add(ad.ToString(), contract);
                                CoordinatorFactory.RemoveRemoteInstanceConnection(ad.ToString());
                                Console.WriteLine(String.Format("Novo coordenador encontrado. Nome do grupo: {0}. Endereço IP: {1}. Id: {2}",contract.Name, ad.ToString(),contract.Id));

                            }
                            catch(Exception ex)
                            {
                                Console.WriteLine("Erro na busca de coordenadores: " + ex.Message);
                                continue;
                            }
                        }
                    }
                }
            }
        }

        void checkGroups()
        {


            while (true)
            {
                try
                {
                    Console.WriteLine("Checagem de grupos parada no delay.");
                    Thread.Sleep(Utilities.KnownCoordinatorsVerificationDelay);
                    Console.WriteLine("Checagem de grupos vai começar.");
                    if (_status != CoordinatorStatus.Sleeping)
                    {
                        lock (_knownCoordinators)
                        {
                            Console.WriteLine("Checagem de grupos com identificadores iguais começou. Lista de coordenadores bloqueada.");

                            foreach (var coordContract in _knownCoordinators)
                            {
                                if (this._group.Id == coordContract.Value.Id)
                                {

                                    lock (_processes)
                                    {
                                        Console.WriteLine("Checagem de grupos bloqueou a lista de processos.");

                                        try
                                        {
                                            if ((ProcessFactory.GetRemoteInstance(coordContract.Key).getContract().Score) < (this.getHostProcessContract().Score))
                                            {
                                                IMongabaCoordinator coord = CoordinatorFactory.GetRemoteInstance(coordContract.Key);
                                                coord.ReceiveNewCoordinator(this.getContract());
                                                Console.WriteLine(String.Format("Solicitando a união de grupos ao coordenador {0}.", coordContract.Key));
                                            }
                                        }
                                        catch (ThreadAbortException)
                                        {
                                            Console.WriteLine("Thread de checagem de grupos terminou .");
                                            return;
                                        }
                                        catch (Exception ex)
                                        {
                                            Console.WriteLine("Erro na checagem de grupos: " + ex.Message);
                                            continue;
                                        }
                                    }
                                }
                            }
                        }

                        Console.WriteLine("Checagem de grupos com identificadores iguais terminada. Lista de coordenadores desbloqueada.");
                    }
                    else
                    {
                        Console.WriteLine("Coordenador Sleeping. Checagem não realizada.");
                    }
                }
                catch (ThreadAbortException)
                {
                    Console.WriteLine("Thread de checagem de grupos terminou .");
                    return;
                }
            }

           
        }

        public void ReceiveNewCoordinator(CoordinatorContract coord)
        {
            lock (_processes)
            {
                Console.WriteLine(String.Format("Repassando a coordenação para o coordenador: {0}.", coord.Id));
                foreach (ProcessContract p in _processes.Values)
                {
                    try
                    {
                        if (p.Address == Utilities.HostIp.ToString())
                        {
                            ProcessFactory.GetLocalInstance().ReceiveNewCoordinator(coord.Address);
                        }
                        else
                        {
                            IMongabaProcess mProcess = ProcessFactory.GetRemoteInstance(p.Address);
                            mProcess.ReceiveNewCoordinator(coord.Address);
                            ProcessFactory.RemoveRemoteInstanceConnection(p.Address);
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Erro no repasse de coordenação " + ex.Message);
                        continue;
                    }
                }

                Pause();

            }
        }

        #endregion


    }
}
